/************************************************************************
* image.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#include "graphics.h"
#include "file.h"

#include <string.h>

static uint32_t merge_colours(uint32_t btm, uint32_t top)
{
	fcolour_t f;
	fcolour_t t;
	fcolour_t r;

	f.r = (float)RED(top);
	f.g = (float)GREEN(top);
	f.b = (float)BLUE(top);
	f.a = (float)ALPHA(top);
	f.a /= 255.0;
	t.r = (float)RED(btm);
	t.g = (float)GREEN(btm);
	t.b = (float)BLUE(btm);
	t.a = (float)ALPHA(btm);
	t.a /= 255.0;

	if (ALPHA(btm) == 0)
		return top;
	if (ALPHA(top) == 0)
		return btm;

	r.r = f.r * (1.0 - t.a) + (f.r * f.a + t.r * (1.0 - f.a)) * t.a;
	r.g = f.g * (1.0 - t.a) + (f.g * f.a + t.g * (1.0 - f.a)) * t.a;
	r.b = f.b * (1.0 - t.a) + (f.b * f.a + t.b * (1.0 - f.a)) * t.a;
	if (t.a == 1.0) {
		r.a = 255.0;
	}else{
		r.a = t.a * f.a * 255.0;
	}

	return C2P(r);
}

static uint32_t colourise(uint32_t btm, uint32_t top)
{
	fcolour_t f;
	fcolour_t t;
	fcolour_t r;

	f.r = (float)RED(top);
	f.g = (float)GREEN(top);
	f.b = (float)BLUE(top);
	f.a = (float)ALPHA(top);
	f.a /= 255.0;
	t.r = (float)RED(btm);
	t.g = (float)GREEN(btm);
	t.b = (float)BLUE(btm);
	t.a = (float)ALPHA(btm);
	t.a /= 255.0;

	if (ALPHA(btm) == 0 || ALPHA(top) == 0)
		return btm;

	r.r = f.r * (1.0 - t.a) + (f.r * f.a + t.r * (1.0 - f.a)) * t.a;
	r.g = f.g * (1.0 - t.a) + (f.g * f.a + t.g * (1.0 - f.a)) * t.a;
	r.b = f.b * (1.0 - t.a) + (f.b * f.a + t.b * (1.0 - f.a)) * t.a;
	r.a = (float)ALPHA(btm);

	return C2P(r);
}

/* load an image to pixel data */
image_t *image_load(char* type, char* file)
{
	/* storage space for the image */
	file_t* f = NULL;
	image_t* image = NULL;

	/* load the file data */
	f = file_load(type,file);

	if (!f)
		return NULL;

	/* and make in image out of it */
	image = image_load_frommem(f);

	file_free(f);

	return image;
}

/* load an image to pixel data from memory */
image_t *image_load_frommem(file_t *f)
{
	image_t* image = malloc(sizeof(image_t));
	if (!image)
		return NULL;

	image->pixels = NULL;
	image->w = 0;
	image->h = 0;

	/* bmp image */
	if (image_is_bmp(f)) {
		if (image_load_bmp(f,image)) {
			vlprintf(CN_ERROR, "Image Not Loaded: %s",f->name);
			free(image);
			image = NULL;
		}
	/* png image */
	}else if (image_is_png(f)) {
		if (image_load_png(f,image)) {
			vlprintf(CN_ERROR, "Image Not Loaded: %s",f->name);
			free(image);
			image = NULL;
		}
	/* tga image */
	}else if (image_is_tga(f)) {
		if (image_load_tga(f,image)) {
			vlprintf(CN_ERROR, "Image Not Loaded: %s",f->name);
			free(image);
			image = NULL;
		}
	/* unsupported image */
	}else{
		vlprintf(CN_ERROR, "Unsupported Image: %s",f->name);
		free(image);
		image = NULL;
	}

	return image;
}

/* load an image to pixel data from a section of the screen */
image_t *image_load_fromscreen(int x, int y, int w, int h, int alpha)
{
	int size;
	image_t *image = malloc(sizeof(image_t));
	if (!image)
		return NULL;

	size = w*h*4;

	image->pixels = malloc(size);
	image->w = w;
	image->h = h;

	/* if keeping alpha, just grab rgba */
	if (alpha) {
		glReadPixels(x,(wm_data.size.height-y)-h,w,h,GL_RGBA,GL_UNSIGNED_BYTE,image->pixels);
	/* otherwise grab rgb, then reformat with alpha */
	}else{
		int i;
		int o;
		image_t img;
		img.w = w;
		img.h = h;
		img.pixels = malloc(size);

		glReadPixels(x,h,w,(wm_data.size.height-y)-h,GL_RGB,GL_UNSIGNED_BYTE,img.pixels);

		for (i=0,o=0; o<size;) {
			image->pixels[o++] = 0xFF;
			image->pixels[o++] = img.pixels[i++];
			image->pixels[o++] = img.pixels[i++];
			image->pixels[o++] = img.pixels[i++];
		}

		free(img.pixels);
	}

	return image;
}

/* create an image from rgba values */
image_t *image_rgba(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
	char n[100];
	image_t *img;
	int size;
	int i;

	snprintf(n,100,"rgba-%u-%u-%u-%u-%d-%d",(uint32_t)r,(uint32_t)g,(uint32_t)b,(uint32_t)a,x,y);

	img = malloc(sizeof(image_t));
	if (!img)
		return NULL;

	img->w = x;
	img->h = y;

	size = x*y*4;

	img->pixels = malloc(sizeof(uint8_t)*size);
	if (!img->pixels) {
		free(img);
		return NULL;
	}

	i = 0;
	while (i<size) {
		img->pixels[i++] = r;
		img->pixels[i++] = g;
		img->pixels[i++] = b;
		img->pixels[i++] = a;
	}

	return img;
}

/* create a copy of an image */
image_t *image_copy(image_t *img)
{
	int ds;
	image_t *p = malloc(sizeof(image_t));
	if (!p)
		return NULL;

	p->w = img->w;
	p->h = img->h;

	ds = p->w*p->h*4;
	p->pixels = malloc(sizeof(unsigned char)*ds);
	if (!p->pixels) {
		free(p);
		return NULL;
	}

	memcpy(p->pixels,img->pixels,ds);

	return p;
}

/* clear an image - set all pixels black */
void image_clear(image_t *img)
{
	int i;
	int s;
	uint32_t c = 0xFF000000;
	if (!img)
		return;
	s = img->w*img->h;
	for (i=0; i<s; i++) {
		SETPXI(img,i,c);
	}
}

/* draw an open rectangle of colour onto an image */
void image_draw_rect(image_t *img, colour_t *c, int width, rect_t *area)
{
	rect_t p;
	rect_t f;
	rect_t t;
	int w;
	if (!img)
		return;
	if (area) {
		p = *area;
		if (p.x < 0) {
			p.w += p.x;
			p.x = 0;
		}
		if (p.y < 0) {
			p.h += p.y;
			p.y = 0;
		}
		if (p.x+p.w > img->w)
			p.w = img->w-p.x;
		if (p.y+p.h > img->h)
			p.h = img->h-p.y;
	}else{
		p.x = 0;
		p.y = 0;
		p.w = img->w;
		p.h = img->h;
	}
	w = (width/2);
	p.w += p.x;
	p.h += p.y;
	f.x = p.x+w;
	f.y = p.y;
	t.x = p.w+w;
	t.y = p.y;
	image_draw_line(img,c,width,&f,&t);
	f.x = p.w;
	f.y = p.y+w;
	t.x = p.w;
	t.y = p.h+w;
	image_draw_line(img,c,width,&f,&t);
	f.x = p.x-(width-w);
	f.y = p.h;
	t.x = p.w-(width-w);
	t.y = p.h;
	image_draw_line(img,c,width,&f,&t);
	f.x = p.x;
	f.y = p.y-(width-w);
	t.x = p.x;
	t.y = p.h-(width-w);
	image_draw_line(img,c,width,&f,&t);
}

/* draw a line of colour onto an image */
void image_draw_line(image_t *img, colour_t *c, int width, rect_t *from, rect_t *to)
{
	int i;
	int dx;
	int dy;
	int sdx;
	int sdy;
	int dxabs;
	int dyabs;
	int x;
	int y;
	int px;
	int py;
	int w;
	uint32_t clr;
	uint32_t tclr;
	if (!img || !from || !to)
		return;

	clr = C2P(*c);

	dx=to->x-from->x;      /* the horizontal distance of the line */
	dy=to->y-from->y;      /* the vertical distance of the line */
	dxabs=abs(dx);
	dyabs=abs(dy);
	sdx=SGN(dx);
	sdy=SGN(dy);
	x=dyabs>>1;
	y=dxabs>>1;
	px=from->x;
	py=from->y;

	width /= 2;

	if (dxabs>=dyabs) { /* the line is more horizontal than vertical */
		for (i=0;i<dxabs;i++) {
			y+=dyabs;
			if (y>=dxabs) {
				y-=dxabs;
				py+=sdy;
			}
			px+=sdx;
			if (px >= img->w || px < 0)
				continue;
			for (w=py-width; w<=py+width; w++) {
				if (w >= img->h || w < 0)
					continue;
				tclr = GETPX(img,px,w);
				tclr = merge_colours(tclr,clr);
				SETPX(img,px,w,tclr);
			}
		}
	}else{ /* the line is more vertical than horizontal */
		for (i=0;i<dyabs;i++) {
			x+=dxabs;
			if (x>=dyabs) {
				x-=dyabs;
				px+=sdx;
			}
			py+=sdy;
			if (py >= img->h || py < 0)
				continue;
			for (w=px-width; w<=px+width; w++) {
				if (w >= img->w || w < 0)
					continue;
				tclr = GETPX(img,w,py);
				tclr = merge_colours(tclr,clr);
				SETPX(img,w,py,tclr);
			}
		}
	}
}

/* draw a rectangle of colour onto an image */
void image_fill_rect(image_t *img, colour_t *c, rect_t *area)
{
	int x;
	int y;
	rect_t p;
	uint32_t clr;
	if (!img)
		return;
	if (area) {
		p = *area;
		if (p.x < 0) {
			p.w += p.x;
			p.x = 0;
		}
		if (p.y < 0) {
			p.h += p.y;
			p.y = 0;
		}
		if (p.x+p.w > img->w)
			p.w = img->w-p.x;
		if (p.y+p.h > img->h)
			p.h = img->h-p.y;
	}else{
		p.x = 0;
		p.y = 0;
		p.w = img->w;
		p.h = img->h;
	}
	p.w += p.x;
	p.h += p.y;
	clr = C2P(*c);
	for (y=p.y; y<p.h; y++) {
		for (x=p.x; x<p.w; x++) {
			SETPX(img,x,y,clr);
		}
	}
}

/* copy pixels from one image to another */
void image_copy_area(image_t *dest, image_t *src, rect_t *to, rect_t *from)
{
	int x;
	int y;
	int fx;
	int fy;
	rect_t t;
	rect_t f;
	uint32_t clr;
	if (!src)
		return;
	if (!dest)
		return;
	if (from) {
		f = *from;
		if (f.x < 0) {
			f.w += f.x;
			f.x = 0;
		}
		if (f.y < 0) {
			f.h += f.y;
			f.y = 0;
		}
		if (f.x+f.w > src->w)
			f.w = src->w-f.x;
		if (f.y+f.h > src->h)
			f.h = src->h-f.y;
	}else{
		f.x = 0;
		f.y = 0;
		f.w = src->w;
		f.h = src->h;
	}
	if (to) {
		t = *to;
		if (t.x < 0) {
			t.w += t.x;
			t.x = 0;
		}
		if (t.y < 0) {
			t.h += t.y;
			t.y = 0;
		}
		if (t.x+t.w > dest->w)
			t.w = dest->w-t.x;
		if (t.y+t.h > dest->h)
			t.h = dest->h-t.y;
	}else{
		t.x = 0;
		t.y = 0;
		t.w = dest->w;
		t.h = dest->h;
	}
	if (t.w != f.w) {
		x = t.w;
		if (x > f.w)
			x = f.w;
		t.w = x;
		f.w = x;
	}
	if (t.h != f.h) {
		y = t.h;
		if (y > f.h)
			y = f.h;
		t.h = y;
		f.h = y;
	}
	t.w += t.x;
	t.h += t.y;
	f.w += f.x;
	f.h += f.y;
	for (y=t.y,fy=f.y; y<t.h; y++,fy++) {
		for (x=t.x,fx=f.x; x<t.w; x++,fx++) {
			clr = GETPX(src,fx,fy);
			SETPX(dest,x,y,clr);
		}
	}
}

/* blit one image over another */
void image_blit(image_t *dest, image_t *src, rect_t *to, rect_t *from)
{
	int x;
	int y;
	int fx;
	int fy;
	rect_t t;
	rect_t f;
	uint32_t fclr;
	uint32_t tclr;
	if (!src)
		return;
	if (!dest)
		return;
	if (from) {
		f = *from;
		if (f.x < 0) {
			f.w += f.x;
			f.x = 0;
		}
		if (f.y < 0) {
			f.h += f.y;
			f.y = 0;
		}
		if (f.x+f.w > src->w)
			f.w = src->w-f.x;
		if (f.y+f.h > src->h)
			f.h = src->h-f.y;
	}else{
		f.x = 0;
		f.y = 0;
		f.w = src->w;
		f.h = src->h;
	}
	if (to) {
		t = *to;
		if (t.x < 0) {
			t.w += t.x;
			t.x = 0;
		}
		if (t.y < 0) {
			t.h += t.y;
			t.y = 0;
		}
		if (t.x+t.w > dest->w)
			t.w = dest->w-t.x;
		if (t.y+t.h > dest->h)
			t.h = dest->h-t.y;
	}else{
		t.x = 0;
		t.y = 0;
		t.w = dest->w;
		t.h = dest->h;
	}
	if (t.w != f.w) {
		x = t.w;
		if (x > f.w)
			x = f.w;
		t.w = x;
		f.w = x;
	}
	if (t.h != f.h) {
		y = t.h;
		if (y > f.h)
			y = f.h;
		t.h = y;
		f.h = y;
	}
	t.w += t.x;
	t.h += t.y;
	f.w += f.x;
	f.h += f.y;
	for (y=t.y,fy=f.y; y<t.h; y++,fy++) {
		for (x=t.x,fx=f.x; x<t.w; x++,fx++) {
			fclr = GETPX(src,fx,fy);
			tclr = GETPX(dest,x,y);
			tclr = merge_colours(tclr,fclr);
			SETPX(dest,x,y,tclr);
		}
	}
}

/* colourise an image */
void image_colourise(image_t *img, colour_t *c, rect_t *area)
{
	int x;
	int y;
	rect_t t;
	uint32_t fclr;
	uint32_t tclr;
	if (!img)
		return;
	if (area) {
		t = *area;
		if (t.x < 0) {
			t.w += t.x;
			t.x = 0;
		}
		if (t.y < 0) {
			t.h += t.y;
			t.y = 0;
		}
		if (t.x+t.w > img->w)
			t.w = img->w-t.x;
		if (t.y+t.h > img->h)
			t.h = img->h-t.y;
	}else{
		t.x = 0;
		t.y = 0;
		t.w = img->w;
		t.h = img->h;
	}
	t.w += t.x;
	t.h += t.y;
	for (y=t.y; y<t.h; y++) {
		for (x=t.x; x<t.w; x++) {
			fclr = C2P(*c);
			tclr = GETPX(img,x,y);
			tclr = colourise(tclr,fclr);
			SETPX(img,x,y,tclr);
		}
	}
}
